package com.wayhome.srpingbootmybatis.metrics.web;

import com.wayhome.srpingbootmybatis.metrics.PrefixFilteredCollectorRegistry;
import io.prometheus.client.Counter;
import io.prometheus.client.Histogram;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.HandlerMapping;

import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

@Configuration
public class WebRequestMetrics {

    private static Logger logger = LoggerFactory.getLogger(WebRequestMetrics.class);

    @Bean
    public RequestInvokeMethodPointCut mvcInvokeMethodPointCut() {
        return new RequestInvokeMethodPointCut();
    }

    @Aspect
    public static class RequestInvokeMethodPointCut  {

        private static ConcurrentHashMap<String, HistogramWrapper> histogramConcurrentHashMap = new ConcurrentHashMap<>();
        private static final String ERROR_TAG = "other";
        private static Histogram  summaryHistogram = Histogram.build("doctor_examine_service","the total of the doctor examine service").labelNames("type").register(PrefixFilteredCollectorRegistry.getInstance());
        private static Counter    errorCounter     = Counter.build("doctor_examine_service_error", "the total of the doctor examine service error").register(PrefixFilteredCollectorRegistry.getInstance());

        @Around("execution(* org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(..)))")
        public Object methodInvoke(ProceedingJoinPoint jointPoint) throws Throwable{
            HistogramWrapper wrapper = null;
            try {
                Object[] args = jointPoint.getArgs();
                HttpServletRequest request = (HttpServletRequest)args[0];
                String metricsName = getMetricsName(request, false);
                if (histogramConcurrentHashMap.get(metricsName) == null) {
                    histogramConcurrentHashMap.computeIfAbsent(metricsName, key -> new HistogramWrapper(metricsName, metricsName, null));
                }
                wrapper = histogramConcurrentHashMap.get(metricsName).start(null);
            } catch (Throwable e) {
            }
            try {
                return jointPoint.proceed();
            } catch (Throwable throwable) {
                errorCounter.inc();
                throw throwable;
            } finally {
                if (wrapper != null) {
                    wrapper.stop();
                }
            }
        }

        static class HistogramWrapper {

            private Histogram delegete  = null;
            private String typeValue    = "error";

            private ThreadLocal<List<Histogram.Timer>> timerCache = ThreadLocal.withInitial(() -> new ArrayList<>());

            public HistogramWrapper(String metrics, String help, String... labelNames) {
                try {
                    if (metrics.startsWith("web")) {
                        typeValue = "web";
                    } else if (metrics.startsWith("api")) {
                        typeValue = "api";
                    } else {
                        typeValue = ERROR_TAG;
                    }
                    if (labelNames != null && labelNames.length > 0) {
                        delegete = Histogram.build(metrics, help).labelNames(labelNames).register(PrefixFilteredCollectorRegistry.getInstance());
                    } else {
                        delegete = Histogram.build(metrics, help).register(PrefixFilteredCollectorRegistry.getInstance());
                    }
                } catch (Exception ex) {
                    logger.error("build histogram error : {} , for metrics: {} ", ex.getMessage(), metrics);
                }
            }

            public HistogramWrapper start(String... labelValues) {
                timerCache.get().add(summaryHistogram.labels(typeValue).startTimer());
                if (delegete != null) {
                    if (labelValues != null && labelValues.length > 0) {
                        timerCache.get().add(delegete.labels(labelValues).startTimer());
                    } else {
                        timerCache.get().add(delegete.startTimer());
                    }
                }
                return this;
            }

            public void stop() {
                try {
                    for (Histogram.Timer timer : timerCache.get()) {
                        timer.observeDuration();
                    }
                } catch (Exception ex) {
                } finally {
                    timerCache.get().clear();
                }

            }
        }

        protected String getMetricsName(HttpServletRequest request, boolean merge) {
            try {
                String uriPattern = String.valueOf(request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE));
                String[] uriPaths = uriPattern.split("/");
                int maxLevel = merge ? Math.min(2, uriPaths.length - 1) : uriPaths.length - 1;
                StringBuilder metricsNameBuilder = new StringBuilder();
                for (int i = 1; i <= maxLevel; i++) {
                    String part = uriPaths[i];
                    if (i == 1) {
                        part = part.replaceAll("[^a-zA-Z_:]","");
                    }
                    metricsNameBuilder.append(part).append("_");
                }
                if (metricsNameBuilder.length() > 1) {
                    metricsNameBuilder.deleteCharAt(metricsNameBuilder.length() - 1);
                }
                return metricsNameBuilder.toString().replaceAll("[^a-zA-Z0-9_:]", "");
            } catch (Exception ex) {
                return ERROR_TAG;
            }
        }

    }


}
